<?php
/**
 * 单一窗口 * 公共 Component
 */
namespace ordersync\component;

use app\common\model\Area;
use request\RequestException;
use RobRichards\XMLSecLibs\XMLSecurityDSig;
use RobRichards\XMLSecLibs\XMLSecurityKey;

trait Customs {

    protected static $priKey;
    protected static $cert;

    // 数据签名
    private function strSign($str) {
        $this->loadCert();
        openssl_sign($str, $signMsg, static::$priKey, 'SHA256'); // 签名
        return base64_encode($signMsg); //base64转码加密信息
    }

    // 数据加密
    private function strEncrypt($str) {
        $this->loadCert();
        openssl_private_encrypt($str, $signMsg, static::$priKey); //注册生成加密信息
        return base64_encode($signMsg); //base64转码加密信息
    }

    // 证书加载
    private function loadCert() {
        if(!static::$priKey) {
            $pfxPath = config('customs_config.pfxPath'); //密钥文件路径
            $priKeyContent = file_get_contents($pfxPath); //获取密钥文件内容

            //私钥加密
            openssl_pkcs12_read($priKeyContent, $certs, config('customs_config.pfxPassword')); //读取公钥、私钥
            static::$priKey = $certs['pkey']; //私钥
            static::$cert = $certs['cert']; // 证书
        }
    }

    /**
     * 生成压缩包
     * @param $order
     * @return string
     * @throws RequestException
     */
    private function generateZip($order) {
        $guid = $this->getGuidOnlyValue();
        $guid1 = $this->GetRandStr(3).$guid;
        $guid2 = $this->GetRandStr(3).$guid;
        $tmp_amount = 0;
        $total = 0;
        $goodsModel = model('goods');
        $goods_list = [];
        foreach($order['extend_order_goods'] as $key => $item) {
            if($item['goods_type'] == 5) continue;  // 赠品不上传
            $goods = $goodsModel->getGoodsInfo(['goods_id' => $item['goods_id']]);
            $goods_common = $goodsModel->getGoodsCommonInfoByID($goods['goods_commonid']);
            $goods_list[] = [
                'ceb:OrderList' => [
                    'ceb:gnum'          => $key + 1,                // 序号
                    'ceb:itemNo'        => $goods['goods_serial'] ?: $goods['goods_commonid'],  // 企业商品货号
                    'ceb:itemName'      => $goods['goods_name'],    // 企业商品名称
                    'ceb:gmodel'        => $goods_common['declare_spec'],    // 企业商品规格
                    'ceb:itemDescribe'  => '',                      // 企业商品描述
                    'ceb:barCode'       => '',                      // 条形码
                    'ceb:unit'          => $goods['declare_unit'],        // 计量单位
                    'ceb:qty'           => $goods['declare_num'] * $item['goods_num'],                  // 申报数量
                    'ceb:price'         => $goods['declare_price'],       // 申报单价
                    'ceb:totalPrice'    => $goods['declare_price'] * $goods['declare_num'] * $item['goods_num'],       // 总价
                    'ceb:currency'      => 142,                     // 币种：人民币
                    'ceb:country'       => dict_convert('country_id_code', $goods['country']),          // 国家代码
                    'ceb:note'          => '',
                ]
            ];
            $tax = $goods['tax_rate']/100
                * $goods['declare_price']
                * $goods['declare_num']
                * $item['goods_num'];
            $total += $tax;
            $trade_mode = $goods['trade_mode'];
            $tmp_amount += $goods['declare_num'];
        }

        if($order['trade_mode']  == TRADE_NORMAL){
            throw new RequestException('订单类型错误');
        }

        $customsConfig = config('customs_config');

        $head = [
            'ceb:guid'              =>      $guid2,
            'ceb:appType'           =>      '1',         //企业报送类型。1-新增 2-变更 3-删除。默认为1
            'ceb:appTime'           =>      date('YmdHis'),    //企业报送时间
            'ceb:appStatus'         =>      '1',         //业务状态:1-暂存,2-申报,默认为1。填写2时,Signature节点必须填写
            'ceb:orderType'         =>      'I',         //电子订单类型：I进口
            'ceb:orderNo'           =>      $order['order_sn'],    //交易平台的订单编号，同一交易平台的订单编号应唯一。长度不能超过60
            'ceb:ebpCode'           =>      $customsConfig['ebpCode'],    //电商平台的海关注册登记编号
            'ceb:ebpName'           =>      $customsConfig['ebpName'],      // 电商平台名称
            'ceb:ebcCode'           =>      $customsConfig['ebcCode'],      // 电商企业代码
            'ceb:ebcName'           =>      $customsConfig['ebcName'],      // 电商企业名称
            'ceb:goodsValue'        =>      $order['goods_amount'],      //商品实际成交价，含非现金抵扣金额
            'ceb:freight'           =>      '0',         //不包含在商品价格中的运杂费，无则填写"0"。
            'ceb:discount'          =>      '0',              //使用积分、虚拟货币、代金券等非现金支付金额，无则填写"0"
            'ceb:taxTotal'          =>      '0',        //企业预先代扣的税款金额，无则填写“0”
            'ceb:acturalPaid'       =>      $order['order_amount'],     //商品价格+运杂费+代扣税款-非现金抵扣金额，与支付凭证的支付金额一致。
            'ceb:currency'          =>      '142',            //限定为人民币，填写“142”
            'ceb:buyerRegNo'        =>      $order['buyer_id'],       // 订购人注册号     //订购人的交易平台注册号(可以传自家平台的ID,也可以随便传,不校验)
            'ceb:buyerName'         =>      $order['extend_order_common']['reciver_name'],  // 订购人姓名
            'ceb:buyerTelephone'    =>      $order['extend_order_common']['reciver_info']['mob_phone'],  // 订购人手机号
            'ceb:buyerIdType'       =>      '1',              //订购人证件类型，1-身份证,2-其它。限定为身份证，填写“1”
            'ceb:buyerIdNumber'     =>      $order['extend_order_common']['reciver_info']['id_card_no'],  // 订购人证件号码
            'ceb:payCode'           =>      '',               //支付企业代码 支付企业需在JC2006注册备案
            'ceb:payName'           =>      '',               //支付企业名称
            'ceb:payTransactionId'  =>      '',               //支付交易流水号
            'ceb:batchNumbers'      =>      '',               //商品批次号(选填)
            'ceb:consignee'         =>      $order['extend_order_common']['reciver_name'],       //收货人姓名，必须与电子运单的收货人姓名一致。(收货人同订购人信息一致)
            'ceb:consigneeTelephone'=>      $order['extend_order_common']['reciver_info']['mob_phone'],     //收货人联系电话，必须与电子运单的收货人电话一致。
            'ceb:consigneeAddress'  =>      $order['extend_order_common']['reciver_info']['address'],    //收货地址，必须与电子运单的收货地址一致。
            'ceb:consigneeDistrict' =>      Area::getProvinceCodeByCityId($order['extend_order_common']['reciver_city_id']), //收货地址行政区划代码,参照国家统计局公布的国家行政区划标准填制
            'ceb:note'              =>      '',
        ];

        $res = [];
        $res['ceb:CEB311Message'] = [
            '____guid'         =>      $guid1,
            '____version'      =>      '1.0',
            '____xmlns:ceb'    =>      'http://www.chinaport.gov.cn/ceb',
            '____xmlns:xsi'    =>      'http://www.w3.org/2001/XMLSchema-instance',
            'ceb:Order'     =>      [
                'ceb:OrderHead' => $head,
                $goods_list
            ],
            'ceb:BaseTransfer'   =>      [
                'ceb:copCode'   =>      $customsConfig['copCode'],
                'ceb:copName'   =>      $customsConfig['copName'],
                'ceb:dxpMode'   =>      'DXP',
                'ceb:dxpId'     =>      $customsConfig['dxpId'],
                'ceb:note'      =>      'test'
            ],
            //            'ds:Signature' => [
            //	            '____xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#',
            //		        'ds:SignedInfo' => [
            //                    'ds:CanonicalizationMethod' => [
            //                        '____Algorithm' => 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'
            //                    ],
            //                    'ds:SignatureMethod' => [
            //                        '____Algorithm' => 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
            //                    ],
            //                    'ds:Reference' => [
            //                        '____URI' => '',
            //                        'ds:Transforms' => [
            //                            'ds:Transform' => [
            //                                '____Algorithm' => 'http://www.w3.org/2000/09/xmldsig#enveloped-signature'
            //                            ]
            //                        ],
            //                        'ds:DigestMethod' => [
            //                            '____ Algorithm' => 'http://www.w3.org/2000/09/xmldsig#sha1',
            //                        ],
            //                        'ds:DigestValue' => 'SF/P+2sVsRQ9dJIqJSroW6ajb0Y',
            //                    ],
            //                ],
            //                'ds:SignatureValue' => '
            //                    AI0wdcUHaaOR+FZ7W8lN6FrjzS+iru1qoBTCxm4S6knmLFLunPkLueELV69nYZr4x+uCPnNDD/wq
            //                    jSqyPnH2xrrx8EFvsIhxNMCi+IlfS1z440YmeMEXnhff0pxSBGgrhETrq1tqp6QBZE5siBF4ow10
            //                    0Q9RKaB+OMs4AB6I+0g=',
            //                'ds:KeyInfo' => [
            //                    'ds:KeyName' => '0001',
            //                    'ds:X509Data' => [
            //                        'ds:X509Certificate' => '
            //                            MIIEWzCCA8SgAwIBAgIDAJknMA0GCSqGSIb3DQEBBQUAMHYxCzAJBgNVBAYTAmNuMREwDwYDVQQK
            //                            Hgh1NVtQU+NcuDENMAsGA1UECx4EAEMAQTENMAsGA1UECB4EUxdOrDEjMCEGA1UEAx4aTi1W/XU1
            //                            W1BT41y4ZXBjbk4tX8NfAFPRUzoxETAPBgNVBAceCE4cZblef1c6MB4XDTE1MDUxOTAwMDAwMFoX
            //                            DTQ5MTIwODAwMDAwMFowJTEjMCEGA1UEAx4aADEAM1P3W8aUpW1Li9VnDVKhVmhfAFPRUzowgZ8w
            //                            DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKoQc7txXMb5VuXJnALpKQ1mAxFW2hxPwRhXY0mirNIL
            //                            2gLY2z7ysqRvkpSRzr3BEq97xWLlXkQDG1QnzvTvh1YQzrffARSlCy6dYJ3YgM+Bps2NsPXhw1Lk
            //                            vk0wn7LXAskEbwRgLihu1pH6/IGEocgeFusWTXT6B/ppTiEXL/87AgMBAAGjggJGMIICQjALBgNV
            //                            HQ8EBAMCBsAwCQYDVR0TBAIwADCBoAYDVR0jBIGYMIGVgBQsaDiQrlh9ryILr2BYMK/wvmRvlqF6
            //                            pHgwdjELMAkGA1UEBhMCY24xETAPBgNVBAoeCHU1W1BT41y4MQ0wCwYDVQQLHgQAQwBBMQ0wCwYD
            //                            VQQIHgRTF06sMSMwIQYDVQQDHhpOLVb9dTVbUFPjXLhlcGNuTi1fw18AU9FTOjERMA8GA1UEBx4I
            //                            ThxluV5/VzqCASUwHQYDVR0OBBYEFPcD90hfpSLKzuhaE3NvhU1xwHDAMEIGA1UdIAQ7MDkwNwYG
            //                            K4EHAQECMC0wKwYIKwYBBQUHAgEWH2h0dHA6Ly9jcHMuY2hpbmFwb3J0Lmdvdi5jbi9DUFMwQgYD
            //                            VR0fBDswOTA3oDWgM4YxaHR0cDovL2xkYXAuY2hpbmFwb3J0Lmdvdi5jbjo4MDg4L2R6a2EwMDAt
            //                            MTk2LmNybDA9BggrBgEFBQcBAQQxMC8wLQYIKwYBBQUHMAGGIWh0dHA6Ly9vY3NwLmNoaW5hcG9y
            //                            dC5nb3YuY246ODA4ODAqBgorBgEEAalDZAUBBBwWGtbQufq159fTv9qwtsr9vt3W0NDEv6q3osf4
            //                            MBoGCisGAQQBqUNkBQYEDBYKUzAyMDEyMDAzODAaBgorBgEEAalDZAUJBAwWClMwMjAxMjAwMzgw
            //                            EgYKKwYBBAGpQ2QCBAQEFgJDQTASBgorBgEEAalDZAIBBAQWAjE5MBMGBSpWCwcFBAoWCLXn19O/
            //                            2rC2MA0GCSqGSIb3DQEBBQUAA4GBAFqdOOqCs/0zfJj5NM3UPXzAK/yIyx6b8ZEQXuY/aojzE46Q
            //                            QXX1/N+G3DsKPvUhXQj1mAsZQeT0aMiUa1aNCd0P8p+PsfrB9E5oZnFhp4cLDkkuh2gx+MCFOHe2
            //                            oEbi2/nCZpvWRJ34id5szTIw1n96/nrrg2+qFk+ddFr0xRzz
            //                        ',
            //                    ]
            //                ]
            //            ],
        ];
        $xml = new ArrayToXML;
        $s = $xml->toXml($res);
//        $s = $this->generateXMLSignFields($s);        // 暂时去除签名

        $cache_file = RUNTIME_PATH.'declare_temp/'.date('Y-m').'/'.date('dHis-').mt_rand(100000, 999999).'_CEB311Message.xml';
        file_rput_contents($cache_file, $s);

        // 添加到压缩文件
        $zip_path = $this->addToZip($cache_file);

        return $zip_path;
    }

    /**
     * generateXMLSignFields XML生成签名域
     * Use sha256withrsa algorithm to generate XML internal signature
     * @param $xml
     * @return string
     * @throws \Exception
     * @author   liuml  <liumenglei0211@163.com>
     * @DateTime 2018/12/21  16:37
     */
    protected function generateXMLSignFields($xml, $prefix = 'ds')
    {
        // 加载要签名的XML
        $doc = new \DOMDocument();
        $doc->loadXML($xml);

        // 创建一个新的安全对象
        $objDSig = new XMLSecurityDSig($prefix);
        // 使用c14n专属规范化
        $objDSig->setCanonicalMethod(XMLSecurityDSig::C14N);
        // 签名使用 SHA-256
        $objDSig->addReference(
            $doc,
            XMLSecurityDSig::SHA1,
            ['http://www.w3.org/2000/09/xmldsig#enveloped-signature'],
            ['force_uri' => true]
        );

        // 创建一个新的(私有)安全密钥
        $objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, ['type' => 'private']);

        // 如果密钥有密码，则使用它进行设置
        // $objKey->passphrase = '<passphrase>';

        $this->loadCert();
        // 加载私钥
        $objKey->loadKey(static::$priKey);


        // 对XML文件签名
        $objDSig->sign($objKey);

        // 将关联的公钥添加到签名
        $objDSig->add509Cert(static::$cert);

        // 将签名附加到XML
        $objDSig->appendSignature($doc->documentElement);
        // saveXML 里面 LIBXML_NOEMPTYTAG 是为了不简写空值的标签。例：(<test />  => <test></test>)
        // 也可以直接这样写 return $doc->saveXML();
        return $doc->saveXML($doc->documentElement, LIBXML_NOEMPTYTAG);
    }

    /**
     * 添加到压缩文件
     * @param $file
     * @return string
     */
    protected function addToZip($file) {
        $zip_path = $file.'.zip';
        $zip = new \ZipArchive();
        $zip->open($zip_path,\ZipArchive::CREATE);   //打开压缩包
        $zip->addFile($file, 'CEB311Message.xml');   //向压缩包中添加文件
        $zip->close();  //关闭压缩包

        // 压缩后删除源文件
//        unlink($file);
        return $zip_path;
    }

    // 海关生成唯一guid值
    protected function getGuidOnlyValue(){
        $a4 = uniqid().rand(10,99);
        $a4 = $this->insertToStr($a4,4,'-');  //这里是后面两组的唯一值 如5770-A529AD987M
        $a4 = $a4.$this->GetRandStr(1);

        $a1 = $this->GetRandStr(5);
        $a2 = $this->GetRandStr(4);
        $a3 = $this->GetRandStr(4);
        $val = $a1.'-'.$a2.'-'.$a3.'-'.$a4;
        return strtoupper($val);
    }

    /**
     * 指定位置插入字符串
     * @param string $str  原字符串
     * @param int $i    插入位置
     * @param int $substr 插入字符串
     * @return string 处理后的字符串
     */
    protected function insertToStr($str, $i, $substr){
        $startstr="";
        for($j=0; $j<$i; $j++){
            $startstr .= $str[$j];
        }

        //指定插入位置后的字符串
        $laststr="";
        for ($j=$i; $j<strlen($str); $j++){
            $laststr .= $str[$j];
        }

        //将插入位置前，要插入的，插入位置后三个字符串拼接起来
        $str = $startstr . $substr . $laststr;

        //返回结果
        return $str;
    }

    /**
     * 获得指定位数随机数
     * @param int $length  指定位数
     * @return string  处理后的字符串
     */
    protected function GetRandStr($length){
        $str='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $len=strlen($str)-1;
        $randstr='';
        for($i=0;$i<$length;$i++){
            $num=mt_rand(0,$len);
            $randstr .= $str[$num];
        }
        return $randstr;
    }

    /**
     * @return string
     */
    public function getResultName()
    {
        return $this->getApiMethodName();
    }

    /**
     * 验证接口返回数据
     * @param $response
     * @throws RequestException
     */
    protected function checkResponse($response)
    {
        if($response['respCode'] != '0000') {
            throw new RequestException($response['procMessage']);
        }
    }

    /**
     * @param mixed $response
     * @throws \request\RequestException
     */
    public function setResponse($response)
    {
        $this->response = json_decode(json_encode($response->defaultResponse), true);
        $this->checkResponse($this->response);
    }
}


if(!class_exists('ArrayToXML')) {
    class ArrayToXML{

        function arrForeach($data, $deep = 0) {
            if (!is_array ($data)) {
                return false;
            }

            $list = [];
            foreach($data as $key => $value){
                $attrs = [];
                $children = [];
                $content = '';
                if(is_array($value) && is_numeric(implode('', array_keys($value)))) {
                    $children[] = self::arrForeach($value, $deep);
                } elseif (is_array($value)) {
                    foreach($value as $key2 => $value2) {
                        if(strstr($key2, '____')) {
                            $attrs[] = str_replace('____', '', $key2).'="'.$value2.'"';
                        } elseif (is_array($value2)) {
                            $children[] = self::arrForeach([$key2 => $value2], $deep);
                        } else {
                            $children[] = self::arrForeach([$key2 => $value2], $deep + 1);
                        }
                    }
                } else {
                    $content = $value;
                }

                $xml = str_repeat("\t", $deep);
                $xml .= is_numeric($key) ? "" : "<{$key}".($attrs ? ' '. implode(' ', $attrs) : '').">";

                if($children) {
                    if(is_numeric($key)) {
                        $xml .= implode("\n", $children);
                    } else {
                        $xml .= "\n".implode("\n", $children)."\n";
                    }
                    $xml .= str_repeat("\t", $deep);
                } else {
                    $xml .= $content;
                }
                $xml .= is_numeric($key) ? "" : "</{$key}>";

                $list[] = $xml;
            }

            return implode("\n", $list);
        }

        //字符串转xml
        function toXml($post){
            $strHead = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
            $string = $this->arrForeach($post, 0);
            return $strHead.$string;
        }
    }
}